跳到主要内容

Create by fall on 22 Sep 2021 Recently revised in 27 Feb 2023

迭代器

Iterator

迭代器,提供了遍历访问数据的机制,部署在可迭代的数据结构内部或者原型上。

一个对象要能够成为迭代器,它必须有一个 next() 方法,每次执行 next() 方法会返回一个对象,这个对象包含了一个 done 属性(是个布尔值,true 表示可以继续下次迭代)和一个 value 属性(每次迭代的值)

// 手写一个迭代器
function makeIterator (arr){
let index = 0
return {
next(){
return index < arr.length ? {
value:arr[index++],
done:false
}:{done:true}
}
}
}
let blah = makeIterator([10,20,45])

iterable 可迭代数据结构:内部或者原型上必须有一个 Symbol.iterator 属性(如果是异步的则是 Symbol.asyncIterator),这个属性是一个函数,执行后会生成一个迭代器:

let obj = {
[Symbol.iterator]() {
return {
index: 0,
next() {
if (this.index < 3) {
return {value: this.index++, done: false}
} else {
return {done: true}
}
}
}
}
}
for (let x of obj) {
console.log(x) // 0 1 2
}

内置的一些可迭代数据结构有:StringArrayTypedArrayMapSetargumentsNodeList

let si = 'hi'[Symbol.iterator]()
si // StringIterator
si.next() // {value: 'h', done: false}
si.next() // {value: 'i', done: false}
si.next() // {value: undefined, done: true}

for...of:用于遍历可迭代数据结构:

  • 遍历字符串:for...in 获取索引,for...of 获取值;
  • 遍历数组:for...in 获取索引,for...of 获取值;
  • 遍历对象:for...in 获取键,for...of 需自行部署 [Symbol.iterator] 接口;
  • 遍历 Setfor...of 获取值, for (const v of set)
  • 遍历 Mapfor...of 获取键值对,for (const [k, v] of map)
  • 遍历类数组:包含 length 的对象、arguments 对象、NodeList对象(无 Iterator 接口的类数组可用 Array.from() 转换);
// 迭代字符串
for (let x of 'abc') {
console.log(x) // 'a' 'b' 'c'
}

// 迭代数组
for (let x of ['a', 'b', 'c']) {
console.log(x) // 'a' 'b' 'c'
}

// 遍历 Set
let set = new Set(['a', 'b', 'c'])
for (let x of set) {
console.log(x) // 'a' 'b' 'c'
}

// 遍历 Map
let map = new Map([['name', '布兰'], ['age', 12]])
for (let [key, value] of map) {
console.log(key + ': ' + value) // 'name: 布兰' 'age: 12'
}

for...offor...in 对比

共同点:能够通过 breakcontinuereturn 跳出循环

不同点: - for...in 的特点:只能遍历键,会遍历原型上属性,遍历无顺序,适合于对象的遍历; - for...of 的特点:能够遍历值(某些数据结构能遍历键和值,比如 Map),不会遍历原型上的键值,遍历顺序为数据的添加顺序,适用于遍历可迭代数据结构

Generator

  • 生成器函数内部执行一段代码,遇到 yield 关键字,javascript 引擎返回关键字后面的内容给外部,并且暂停该函数的执行;

  • 外部函数可以同步 next 方法恢复函数的执行;

function* 会生成一个函数,该函数不会立即执行,而是返回一个 Generator 对象,这个 Generator 是可以被迭代的。

  • 可以实现暂停执行和恢复执行
  • 返回的 Generator 对象有两个 key,一个是 value 另一个是 done。
  • 如果遇到关键字 yield 就会停止,并且返回该关键字后面的内容作为 value 进行返回
  • 再次调用 .next() 会继续执行,直到遇到下一个 yield 关键字,或者 return 并且返回 return 中的内容
  • function 中的 return 如果不设置,会返回 undefined

示例一

function* gen(){
console.log(1)
yield 'once'
console.log(2)
yield 'twice'
console.log(3)
return 'end'
console.log(4)
}
let go = gen()
go.next()
// 1 -- log 输出
// { value: "once", done: false }
go.next()
// 2 -- log 输出
// { value: "twice", done: false }
go.next()
// 3 -- log 输出
// { value: "end", done: ture }
go.next() // { value: undefined, done: ture }
// 如果没有 return
function* gen2(){
yield 'once'
}
let go2 = gen2()
go2.next() // { value: "once", done: false }
go2.next() // { value: undefined, done: ture }

yield 后面可以添加 * 可以实现多次迭代。

function* inner() {
yield* [1, 2] // 如果向 yield 后面添加 *,此处表示,先返回 1 作为 value,然后返回 2 作为 value
return 'fall'
}
let i = inner()
i.next() // 1

yield 关键字还可以调用函数

function* outer(){
const result = yield* inner() // result 会被赋值为 return 的值,并且上个函数的 return 不会被输出,只会被接受到
// 并且如果不在 yield 后面添加 * 会直接把 inner() 作为 value 进行返回
console.log(result)
}
let i = outer()
i.next() // { value: 1, done: false }
i.next() // { value: 1, done: false }
i.next() // fall {value:undefined,done:true}

实例方法

Generator.prototype.next()

该方法会返回一个带有value done 的对象。如果传入了参数,那么这个参数会传给上一条执行的 yield 语句左边的变量

function* f(es) {
console.log(es)
let a = yield 12
console.log(a)
let b = yield a
console.log(b)
}
let g = f()
console.log(g.next('1212')) // 第一个next里面的值应该会被废弃
console.log(g.next('1313')) // 可以通过 next 中传值,赋值给 a
console.log(g.next('9999'))
// {value: 12, done: false}
// '1313'
// {value: 'b', done: false}
// '9999'
// {value: undefined, done: true}

Generator.prototype.throw()

用于向生成器中抛出异常

function* gen() {
try {
yield 'a'
} catch(e) {
console.log(e)
}
yield 'b'
}
let g = gen()
g.next()
g.throw('error a')
g.next()
// {value: "a", done: false}
// 'error a'
// {value: "b", done: false}
// {value: undefined, done: true}

如果没有补货错误,后面的代码将不会被执行

function* gen() {
yield 'a'
yield 'b'
yield 'c'
}
let g = gen()
g.next()
try {
g.throw('error a')
} catch(e) {
console.log(e)
}
g.next()
// {value: "a", done: false}
// 'error a'
// {value: undefined, done: true}

Generator.prototype.return()

直接结束执行

function* gen() {
yield 1
yield 2
yield 3
}
let g = gen()
g.next() // { value: 1, done: false }
g.return('foo') // {value :"foo",done:true}

应用

将异步操作同步化

比如同时有多个请求,多个请求之间是有顺序的,只能等前面的请求完成了才请求后面的:

function* main() {
let res1 = yield request('a')
console.log(res1)
let res2 = yield request('b')
console.log(res2)
let res3 = yield request('c')
console.log(res3)
}
function request(url) {
setTimeout(function(){ // 模拟异步请求
it.next(url)
}, 300)
}
let it = main()
it.next()
// 'a' 'b' 'c'

给对象部署 Iterator 接口:

function* iterEntries(obj) {
let keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
yield [key, obj[key]]
}
}
obj = { foo: 3, bar: 7 }
for (let [key, value] of iterEntries(obj)) {
console.log(key, value)
}
// foo 3
// bar 7

参考文章

作者链接
大海我来了https://juejin.cn/post/6895898051559456776